#include "filesys/directory.h"
#include <stdio.h>
#include <string.h>
#include <list.h>
#include "filesys/filesys.h"
#include "threads/malloc.h"
#include "threads/thread.h"

/* Creates a directory with space for ENTRY_CNT entries in the
   given SECTOR.  Returns true if successful, false on failure. */
bool
dir_create (disk_sector_t sector, size_t entry_cnt) 
{
	static int temp_counter = 0;
	temp_counter++;
	if(temp_counter > 250)
		return false;
  if (! inode_create (sector, entry_cnt * sizeof (struct dir_entry)) ){
	  return false;
  }

  struct inode *inode = inode_open(sector);
  set_is_dir(inode);
  inode_close(inode);
  return true;
}

/* Opens and returns the directory for the given INODE, of which
   it takes ownership.  Returns a null pointer on failure. */
struct dir *
dir_open (struct inode *inode) 
{
  struct dir *dir = calloc (1, sizeof *dir);
  if (inode != NULL && dir != NULL)
    {
      dir->inode = inode;
      dir->pos = 0;

	  // mark as directory
	  set_is_dir(inode);

	  // if root directory is opened.
	  if(inode_get_inumber(inode) == ROOT_DIR_SECTOR){
		set_parent_dir_idx(inode, ROOT_DIR_SECTOR);
	  }
      return dir;
    }
  else
    {
      inode_close (inode);
      free (dir);
      return NULL; 
    }
}

/* Opens the root directory and returns a directory for it.
   Return true if successful, false on failure. */
struct dir *
dir_open_root (void)
{
  return dir_open (inode_open (ROOT_DIR_SECTOR));
}

/* Opens and returns a new directory for the same inode as DIR.
   Returns a null pointer on failure. */
struct dir *
dir_reopen (struct dir *dir) 
{
	if(dir==NULL)
		return NULL;
  return dir_open (inode_reopen (dir->inode));
}

/* Destroys DIR and frees associated resources. */
void
dir_close (struct dir *dir) 
{
  if (dir != NULL)
    {
      inode_close (dir->inode);
      free (dir);
    }
}

/* Returns the inode encapsulated by DIR. */
struct inode *
dir_get_inode (struct dir *dir) 
{
	if(dir==NULL)
		return NULL;
  return dir->inode;
}

int get_pos(struct dir *dir)
{
	return dir->pos;
}

void set_pos(struct dir *dir, int position)
{
	dir->pos = position;
}

/* Searches DIR for a file with the given NAME.
   If successful, returns true, sets *EP to the directory entry
   if EP is non-null, and sets *OFSP to the byte offset of the
   directory entry if OFSP is non-null.
   otherwise, returns false and ignores EP and OFSP. */
static bool
lookup (const struct dir *dir, const char *name,
        struct dir_entry *ep, off_t *ofsp) 
{
  struct dir_entry e;
  size_t ofs;
  
  ASSERT (dir != NULL);
  ASSERT (name != NULL);

  for (ofs = 0; inode_read_at (dir->inode, &e, sizeof e, ofs) == sizeof e;
       ofs += sizeof e) 
    if (e.in_use && !strcmp (name, e.name)) 
      {
        if (ep != NULL)
          *ep = e;
        if (ofsp != NULL)
          *ofsp = ofs;
        return true;
      }
  return false;
}

/* Searches DIR for a file with the given NAME
   and returns true if one exists, false otherwise.
   On success, sets *INODE to an inode for the file, otherwise to
   a null pointer.  The caller must close *INODE. */
bool
dir_lookup (const struct dir *dir, const char *name,
            struct inode **inode) 
{
  struct dir_entry e;

  ASSERT (dir != NULL);
  ASSERT (name != NULL);

  if (lookup (dir, name, &e, NULL))
    *inode = inode_open (e.inode_sector);
  else
    *inode = NULL;

  return *inode != NULL;
}

/* Adds a file named NAME to DIR, which must not already contain a
   file by that name.  The file's inode is in sector
   INODE_SECTOR.
   Returns true if successful, false on failure.
   Fails if NAME is invalid (i.e. too long) or a disk or memory
   error occurs. */
bool
dir_add (struct dir *dir, const char *name, disk_sector_t inode_sector) 
{
  struct dir_entry e;
  off_t ofs;
  bool success = false;
  
  ASSERT (dir != NULL);
  ASSERT (name != NULL);

  /* Check NAME for validity. */
  if (*name == '\0' || strlen (name) > NAME_MAX)
    return false;

  /* Check that NAME is not in use. */
  if (lookup (dir, name, NULL, NULL))
    goto done;

  /* Set OFS to offset of free slot.
     If there are no free slots, then it will be set to the
     current end-of-file.
     
     inode_read_at() will only return a short read at end of file.
     Otherwise, we'd need to verify that we didn't get a short
     read due to something intermittent such as low memory. */
  for (ofs = 0; inode_read_at (dir->inode, &e, sizeof e, ofs) == sizeof e;
       ofs += sizeof e) 
    if (!e.in_use)
      break;

  /* Write slot. */
  e.in_use = true;
  strlcpy (e.name, name, sizeof e.name);
  e.inode_sector = inode_sector;
  success = inode_write_at (dir->inode, &e, sizeof e, ofs) == sizeof e;

// update directories parent_idx if successfuly written
	if(success){
		struct inode *temp = inode_open(inode_sector);
		set_parent_dir_idx( temp, inode_get_inumber(dir->inode) );
		inode_close( temp );
	}

 done:
  return success;
}

/* Removes any entry for NAME in DIR.
   Returns true if successful, false on failure,
   which occurs only if there is no file with the given NAME. */
bool
dir_remove (struct dir *dir, const char *name) 
{
  struct dir_entry e;
  struct inode *inode = NULL;
  bool success = false;
  off_t ofs;

  ASSERT (dir != NULL);
  ASSERT (name != NULL);

  /* Find directory entry. */
  if (!lookup (dir, name, &e, &ofs))
    goto done;

  /* Open inode. */
  inode = inode_open (e.inode_sector);
  if (inode == NULL)
    goto done;

  /* Erase directory entry. */
  e.in_use = false;
  if (inode_write_at (dir->inode, &e, sizeof e, ofs) != sizeof e) 
    goto done;

  /* Remove inode. */
  inode_remove (inode);
  success = true;

 done:
  inode_close (inode);
  return success;
}

/* Reads the next directory entry in DIR and stores the name in
   NAME.  Returns true if successful, false if the directory
   contains no more entries. */
bool
dir_readdir (struct dir *dir, char name[NAME_MAX + 1])
{
  struct dir_entry e;

  while (inode_read_at (dir->inode, &e, sizeof e, dir->pos) == sizeof e) 
    {
      dir->pos += sizeof e;
      if (e.in_use)
        {
			strlcpy (name, e.name, NAME_MAX + 1);
//			printf("%s\n", name);
          return true;
        } 
    }
  return false;
}

bool
dir_is_empty (struct dir *dir)
{
	struct dir_entry e;
	size_t ofs;

	for (ofs = 0; inode_read_at (dir->inode, &e, sizeof e, ofs) == sizeof e; ofs += sizeof e) 
		if (e.in_use)
			return false;
  return true;
}


bool get_real_from_full(char *full_path, char *last){
	int len;
	char *tokens = NULL;
	char *save_ptr, *token, *past=NULL;

	if(full_path == NULL)
		return false;

	len = strlen(full_path);
	if(len == 0)
		return false;


	tokens = (char *)malloc( (len+1)*sizeof(char) );
	if(tokens == NULL){
		return false;
	}
	

	strlcpy( tokens, full_path, len+1);

	for(token = strtok_r(tokens, "/ ", &save_ptr); token!=NULL;
		token = strtok_r(NULL, "/ ", &save_ptr) ){
			if( strcmp( token, "." ) == 0)
				continue;
			past = token;
	}
	
	// file name is empty
	if(past == NULL || strlen(past)==0){
		free(tokens);
		last[0] = '\0';
		return false;
	}

	strlcpy( last, past, NAME_MAX+1);
	// if there is no '\0' on the very last, then the name is longer than 14
	if(  strlen(past) > NAME_MAX ){
		free(tokens);
		return false;
	}

	free(tokens);
	return true;
}

struct dir * get_dir_from_full(char *full_path, bool needCurrent){
	int len;
	char *tokens;
	struct dir *cur_dir, *parent_dir;
	char *save_ptr, *token;
	bool isOkay;
	bool lastDir = true;
	bool fromNone = false;
	bool isDoubleDot = false;

	if(full_path == NULL)
		return false;

	len = strlen(full_path);
	if(len == 0)
		return false;


	tokens = (char *)malloc( (len+1)*sizeof(char) );
	if(tokens == NULL){
		return false;
	}

	cur_dir = NULL;
	parent_dir = NULL;

/*	if(len==1 && full_path[0] == '.'){
		return NULL;
	}
	else */
	if(len>=1 && full_path[0] == '/'){
		cur_dir = dir_open_root();

		len -= 1;
		strlcpy( tokens, &full_path[1], len+1);
	}
	else if(len>=2 && full_path[0] == '.' && full_path[1] == '.'){
		cur_dir = dir_open( inode_open(thread_current()->current_dir_idx) );
		int temp_idx = get_parent_dir_idx(cur_dir->inode);
		dir_close(cur_dir);
		
		cur_dir = dir_open( inode_open(temp_idx) );

		len -= 2;
		strlcpy( tokens, &full_path[2], len+1);
		isDoubleDot = true;
	}
	else if(len>=1 && full_path[0] == '.'){
		cur_dir = dir_open( inode_open(thread_current()->current_dir_idx) );

		len -= 1;
		strlcpy( tokens, &full_path[1], len+1);
	}
	else{
		cur_dir = dir_open( inode_open(thread_current()->current_dir_idx) );

//		len -= 0;
		strlcpy( tokens, &full_path[0], len+1);
		fromNone = true;
	}
	
	parent_dir = cur_dir;
	if( inode_get_inumber( cur_dir->inode )!=ROOT_DIR_SECTOR){
//		printf("hello?\n");
		parent_dir = dir_open(  inode_open( get_parent_dir_idx(cur_dir->inode) )  );

		struct dir_entry e;
		size_t ofs;
		bool dirExist = false;

		for (ofs = 0; inode_read_at (parent_dir->inode, &e, sizeof e, ofs) == sizeof e; ofs += sizeof e) 
			if (e.inode_sector == inode_get_inumber( cur_dir->inode ) && e.in_use)
			{
				dirExist = true;
				break;
			}
		if(!dirExist){
			dir_close(cur_dir);
			dir_close(parent_dir);
			return false;
		}
	}

	isOkay = true;
	// first dots are already removed, so it's quite safe
	for(token = strtok_r(tokens, "/ ", &save_ptr); token!=NULL;
		token = strtok_r(NULL, "/ ", &save_ptr) ){
		struct inode *temp;
		lastDir = true;
		
		if( strcmp( token, "." ) == 0)
			continue;

		// not in current directory
		if(!dir_lookup( cur_dir, token, &temp) ){
			lastDir = false;
			// check if that token is the very last one (could be file)

			if( fromNone && dir_lookup( parent_dir, token, &temp) ){
				lastDir = true;
			}

			token = strtok_r (NULL, "/ ", &save_ptr);
			// something is not in dir, but not the very last one, so wrong!
			if (token != NULL)
				isOkay = false;
			break;
		}

		// if temp inode is not a dir
		if( !get_is_dir(temp) ){
			lastDir = false;
			//then it should be file, so we need to check the last
			token = strtok_r (NULL, "/ ", &save_ptr);
			// something is not in dir, but not the very last one, so wrong!
			if (token != NULL)
				isOkay = false;
			break;
		}

		else{
			lastDir = true;
			// it's directory, so shift the directory by one more level deeper
			if (parent_dir != cur_dir)
				dir_close (parent_dir);
			parent_dir = cur_dir;
			cur_dir = dir_open (temp);
		}
	}

	if(!isOkay){
		if (parent_dir != cur_dir)
			dir_close (parent_dir);
		dir_close (cur_dir);
		free(tokens);
		return NULL;
	}
	
	if(needCurrent){
		free(tokens);
		if(isDoubleDot && len == 0){
			if (parent_dir != cur_dir)
				dir_close (parent_dir);
			return cur_dir;
		}
		if(lastDir){
			if (parent_dir != cur_dir)
				dir_close (cur_dir);
			return parent_dir;
		}
		else{
			if (parent_dir != cur_dir)
				dir_close (parent_dir);
			return cur_dir;
		}
	}
	else{
		if (parent_dir != cur_dir)
			dir_close (cur_dir);
		
		free(tokens);
		return parent_dir;
	}
}

bool dir_chdir (const char *full_path){
	char real_name[NAME_MAX+1]={'\0',};
	if(strcmp(full_path, "/")==0){
		thread_current()->current_dir_idx = ROOT_DIR_SECTOR;	
		return true;
	}

	if (!get_real_from_full (full_path, real_name))
		return false;

	// because we don't know the name of current directory, we need to look from parent
	struct dir *dir = get_dir_from_full(full_path, true);
	if(dir == NULL)
		return false;

	if( strcmp(full_path, "..")==0){
		thread_current()->current_dir_idx = inode_get_inumber( dir_get_inode(dir) );
		dir_close(dir);
		return true;
	}

	struct inode *temp;
	// not in current directory
	if(!dir_lookup( dir, real_name, &temp) ){
		dir_close( dir );
		return false;
	}
	// if temp inode is not a dir
	if( !get_is_dir(temp) ){
		dir_close( dir );
		return false;
	}
	// close parent dir
	dir_close( dir );
	dir = dir_open(temp);
	thread_current()->current_dir_idx = inode_get_inumber( dir_get_inode(dir) );
	dir_close( dir );
	return true;
}
